/**
 * 
 */
package ufpr.mestrado.ais.metaheuristics.misa;

import java.util.Iterator;

import jmetal.base.Algorithm;
import jmetal.base.Operator;
import jmetal.base.Problem;
import jmetal.base.Solution;
import jmetal.base.SolutionSet;
import jmetal.base.operator.mutation.NonUniformMutation;
import jmetal.util.AdaptiveGrid;
import jmetal.util.JMException;
import jmetal.util.Ranking;
import ufpr.mestrado.ais.base.AISSolutionSet;
import ufpr.mestrado.ais.util.AISAdaptiveGridArchive;

/**
 * @author bertol
 * 
 */
public class MISA extends Algorithm {

	/**
	 * 
	 */
	private static final long serialVersionUID = 2949940733600903025L;
	/**
	 * stores the problem to solve
	 */
	private Problem problem_;

	public MISA(Problem problem) {
		this.problem_ = problem;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see jmetal.base.Algorithm#execute()
	 */
	@Override
	public SolutionSet execute() throws JMException, ClassNotFoundException {

		int evaluations;

		// tamanho da população
		final int nPop = Integer.parseInt(getInputParameter("nPop").toString());

		// máximo de avaliações
		final int stopCriteria = Integer.parseInt(getInputParameter(
				"stopCriteria").toString());

		// máximo de internal runs

		final int maxSize = Integer.parseInt(getInputParameter("maxSize")
				.toString());

		final int bisections = Integer.parseInt(getInputParameter("bisections")
				.toString());

		// Initialize the variables
		SolutionSet population = new SolutionSet(nPop);
		evaluations = 0;

		// Read the operators
		final Operator hipermutation = operators_.get("hipermutation");

		// cria grid adaptativo
		AISAdaptiveGridArchive archive = new AISAdaptiveGridArchive(maxSize,
				bisections, problem_.getNumberOfObjectives());

		// limiar de escolha de soluções candidatas
		final double threshold = nPop * 0.05d;

		// Create the initial solutionSet
		Solution newSolution;
		for (int i = 0; i < nPop; i++) {
			newSolution = new Solution(problem_);
			problem_.evaluate(newSolution);
			problem_.evaluateConstraints(newSolution);
			population.add(newSolution);
		} // for

		while (evaluations < stopCriteria) {

			// pega todos os anticorpos candidatos
			final SolutionSet choosed = chooseSolutionsToClone(population,
					threshold);

			// adiciona todos os escolhidos na memória secundária
			archive.addAll(choosed);

			final SolutionSet clones = applyBestAntibodyMutation(archive,
					hipermutation);

			// aplica clonagem para os anticorpos menos aptos
			final SolutionSet notSoGood = applyWorstAntibodyMutation(population);

			// join de todas as soluções disponíveis
			population = selectNewSolution(clones, notSoGood, nPop);

		}

		return archive;
	}

	/**
	 * Escolhe quais são os melhores anticorpos para a mutação.
	 * 
	 * @param population
	 *            População atual
	 * @param threshold
	 *            Limiar para escolha das soluções
	 * @return Lista das melhores soluções
	 */
	private SolutionSet chooseSolutionsToClone(final SolutionSet population,
			final double threshold) {

		// lista com as soluções a serem clonadas
		final AISSolutionSet choosed = new AISSolutionSet(population.size());

		// rank para determinar as relações de dominância
		// o rankin também já realiza a comparação em
		// função das restrições violadas.
		final Ranking ranking = new Ranking(population);

		// adiciona todasas soluções da primeira aproximação da fronteira
		choosed.addAll(ranking.getSubfront(0));

		if (choosed.size() < threshold) {
			// pega as demais soluções até formar a porcentagem de soluções
			// serem clonadas
			for (int front = 1; front < ranking.getNumberOfSubfronts(); front++) {

				final SolutionSet incomming = ranking.getSubfront(front);

				// itera sobre todas as fronteiras até obter o percentual
				for (int pointer = 0; pointer < incomming.size()
						&& choosed.size() < threshold; pointer++) {

					// marca a solução
					incomming.get(pointer).marked();

					// adiciona no selecionado
					choosed.add(incomming.get(pointer));
				}

			}
		}
		return choosed;

	}

	/**
	 * Aplica a mutação uniform para as soluçẽos de melhor fitness
	 * 
	 * @param archive
	 * @param hipermutation
	 * @return Lista com as soluções alteradas
	 * @throws JMException
	 */
	private SolutionSet applyBestAntibodyMutation(
			final AISAdaptiveGridArchive archive, final Operator hipermutation)
			throws JMException {

		final SolutionSet clones = new SolutionSet(archive.size() * 60);

		// calcula a média de ocupação dos hipercubos
		final AdaptiveGrid grid = archive.getGrid();

		double avaregeOccupation = 0;

		for (int hCube : grid.getOccupiedHypercubes()) {
			avaregeOccupation += grid.getLocationDensity(hCube);
		}

		// divide pelo total de hipercubos
		avaregeOccupation /= grid.calculateOccupied();

		for (Iterator<? extends Solution> iterator = archive.iterator(); iterator
				.hasNext();) {

			// pré ajuste de clones por solução
			float clonesPerSolution = 60 / archive.size();

			Solution solution = iterator.next();

			// ajusta o total de clones
			if (archive.getGrid().getLocationDensity(
					archive.getGrid().location(solution)) < avaregeOccupation) {
				clonesPerSolution *= 2;
			} else if (archive.getGrid().getLocationDensity(
					archive.getGrid().location(solution)) > avaregeOccupation) {
				clonesPerSolution /= 2;
			}

			// clona
			for (int nClone = 0; nClone < clonesPerSolution; nClone++) {
				final Solution clone = (Solution) hipermutation
						.execute(new Solution(solution));

				problem_.evaluate(clone);

				problem_.evaluateConstraints(clone);

				clones.add(clone);
			}
		}

		return clones;

	}

	/**
	 * Aplica a mutação nas soluções não tão boas com o operador de mutação nõa
	 * linear
	 * 
	 * @param population
	 * @return Lista das soluções mutadas
	 * @throws JMException
	 */
	private SolutionSet applyWorstAntibodyMutation(SolutionSet population)
			throws JMException {

		final SolutionSet mutadedSolutions = new SolutionSet(population.size());

		// utiliza um operador de mutação não uniforme
		final Operator operator = new NonUniformMutation();

		for (Iterator<? extends Solution> iterator = population.iterator(); iterator
				.hasNext();) {

			Solution solution = iterator.next();

			// se solução não está marcada, então não está no arquivo
			if (!solution.isMarked()) {
				mutadedSolutions.add((Solution) operator.execute(solution));

			}

		}

		return mutadedSolutions;
	}

	/**
	 * Monta a nova população
	 * 
	 * @param goodSolutions
	 * @param notSoGoodSolutions
	 * @param nPop
	 * @return Lista com a nova população do problema
	 */
	private SolutionSet selectNewSolution(final SolutionSet goodSolutions,
			final SolutionSet notSoGoodSolutions, final int nPop) {

		// junta todas as soluções do problema
		final AISSolutionSet newPopulation = new AISSolutionSet(nPop);

		// monta rank do problema
		final Ranking ranking = new Ranking(goodSolutions
				.union(notSoGoodSolutions));

		// realiza loop com e monta nova população do problema
		newPopulation.addAll(ranking.getSubfront(0));

		// itera sobre todas as outras fronteiras até onter o total
		for (int front = 1; front < ranking.getNumberOfSubfronts()
				&& newPopulation.size() < nPop; front++) {

			final SolutionSet nfront = ranking.getSubfront(front);

			// se a soma da população atual e dos itens da
			// fronteira a ser selecionada forem
			// menor que o tamanho máximo da população, adiciona tudo
			if (nfront.size() + newPopulation.size() < nPop) {
				newPopulation.addAll(nfront);
			} else {
				// senão, adiciona até o limite estabelecido
				for (int pointer = 0; pointer < nfront.size()
						&& newPopulation.size() < nPop; pointer++) {

					newPopulation.add(nfront.get(pointer));

				}
			}

		}

		// desmarca todas as soluções para a próxima rodada
		for (int pointer = 0; pointer < newPopulation.size(); pointer++) {
			newPopulation.get(pointer).unMarked();

		}

		return newPopulation;
	}
}
